graphile-config
PRERELEASE: this is pre-release software; use at your own risk. This will
likely change a lot before it's ultimately released.
graphile-config
provides a standard plugin interface and helpers that can be
used across the entire of the Graphile suite. Primarily users will only use this
as import type Plugin from 'graphile-config';
so that they can export plugins.
This package provides two interfaces: Plugin
and Preset
(alias Config
).
Plugin
A plugin is responsible for adding capabilities to a Graphile package. Each
Graphile package will register its own "scope" within the plugin's spec;
commonly these scopes may contain capabilities such as 'hooks' or 'events' which
this package attempts to standardize.
A Graphile Plugin is an object with the following properties:
name
(string
): The name of the plugin, this must be unique and will be
used for capabilities such as skipPlugins
version
(string
): a semver-compliant version for the plugin, this would
normally match the version in the package.json
but does not need to (e.g. if
the module in question contains multiple plugins)description
(optional string
): human-readable description of the plugin in
CommonMark (markdown) format.provides
(optional string[]
): an optional list of "feature labels" that
this plugin provides, this is primarily used to govern the order in which the
plugin (and its hooks and events) are executed. Feature labels must be unique
within the list of loaded plugins, for example two different plugins should
not both provide subscriptions
. If unspecified, defaults to the plugin name.after
(optional string[]
): indicates that this plugin should be loaded
after the named features (if present)before
(optional string[]
): indicates that this plugin should be loaded
before the named features (if present)
In addition to the properties above, plugins may also contain properties for
each of the supported scopes, for example there may be a postgraphile
scope
for PostGraphile, or a worker
scope for Graphile Worker. The value for each of
these scopes will be an object, but the contents of that object are defined by
the projects in question.
NOTE: Currently this plugin system is only intended for Graphile usage (and
thus we do not need to "reserve" keys), but should you find it useful for other
projects please reach out via GitHub issues and we can discuss what's necessary
to make this more universal. Should you decide to not heed this advice, please
at least make sure that the "scopes" you add are namespaced in a way to avoid
future conflicts with features we may wish to add.
Preset
A preset bundles together a list of plugins, and options for various of the
"scopes". You may use more than one preset at a time, and presets may also
compose (extend) other presets. When a library is passed a list of presets it
results in a resolved preset (a preset that has no "extends") using the
ResolvePresets
algorithm; broadly all the extends
are resolved in order, the
plugins specified are merged as a set (each plugin will only be included once)
and the options are merged via object merging such that the options specified
last win.
NOTE: if you compose two presets (PresetA and PresetB) that both extends
the same underlying preset (BASE) and apply some overrides, then the overrides
in PresetA will be overridden by re-applying the BASE preset again. For this
reason, presets that are expected to be combined with other presets should not
extends
common/shared presets, instead the end-user should be expected to add
these presets themselves.
NOTE: the order of presets is significant.
ResolvePresets(presets):
- Let {finalPreset} be an empty preset.
- For each {preset} in {presets}:
- Let {resolvedPreset} be {ResolvePreset(preset)}.
- Let {finalPreset} be {MergePreset(finalPreset, resolvedPreset)}.
- Return {finalPreset}.
ResolvePreset(preset):
- Let {presets} be the list specified in the {extends} property of {preset} (or
an empty list if none specified).
- Let {basePreset} be {ResolvePresets(presets)}.
- Return {MergePreset(basePreset, preset)}.
MergePreset(basePreset, extendingPreset):
- Let {finalPreset} be an empty preset.
- Assert: {basePreset} has an empty or non-existent {extends} property.
- Let {plugins} be the list of plugins defined in {basePreset} union the list
of plugins in {extendingPreset}.
- Let the list of plugins for {finalPreset} be {plugins}.
- Let {scopes} be the list of scopes defined in {basePreset} union the list of
scopes in {extendingPreset}.
- For each {scope} in {scopes}:
- Let {baseScope} be the {scope} in {basePreset}.
- Let {extendingScope} be the {scope} in {extendingPreset}.
- If {baseScope} and {extendingScope} both exist:
- Let {scope} in {finalPreset} be the result of merging {baseScope} and
{extendingScope} akin to
Object.assign({}, baseScope, extendingScope)
.
- Else: let {scope} in {finalPreset} be whichever of {baseScope} and
{extendingScope} actually exist.
- Return {finalPreset}.
IMPORTANT: the default
name must not be used as a top-level key in a
preset to enable compatibility with the various ESM emulations.
Crowd-funded open-source software
To help us develop this software sustainably, we ask all individuals and
businesses that use it to help support its ongoing maintenance and development
via sponsorship.
And please give some love to our featured sponsors 🤩:
* Sponsors the entire Graphile suite
Supporting TypeScript ESM
You can specify a graphile.config.ts
file; but if that uses export default
and your TypeScript is configured to export ESM then you'll get an error telling
you that you cannot require
an ES Module:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /path/to/graphile.config.ts
require() of ES modules is not supported.
require() of /path/to/graphile.config.ts from /path/to/node_modules/graphile-config/dist/loadConfig.js is an ES module file as it is a .ts file whose nearest parent package.json contains "type": "module" which defines all .ts files in that package scope as ES modules.
Instead change the requiring code to use import(), or remove "type": "module" from /path/to/package.json.
Or, in newer versions, an error saying unknown file extension:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /path/to/graphile.config.ts
To solve this, use the experimental loaders API to add support for TS ESM via
the ts-node/esm
loader:
export NODE_OPTIONS="$NODE_OPTIONS --loader ts-node/esm"
Then run your command again.